-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
-- Description:
--   This class is responsible for canbus instance management, specified functions are following:
--   1. Management for specified instance on channel
--   2. Functional interfaces of canbus instance, such as send/recv task, or statistics

local log = require 'mc.logging'
local vos = require 'utils.vos'
local definition = require 'canbus.definition'

local canbus_instance = {}
canbus_instance.__index = canbus_instance

-- create canbus specified instance, with driver and other attributes
function canbus_instance.new(id, channel)
    return setmetatable({
        channel = channel,
        identifier = id,
        magic = string.unpack('I4', vos.get_random_array(4)),  -- 4个字节
        send_count = 0,
        send_failed = 0,
        recv_count = 0,
        state = definition.can_state.NORMAL
    }, canbus_instance)
end

-- driver load and initialize
function canbus_instance:init(channel)
    local ok, msg = pcall(function ()
        local canbus_driver = require 'libsoc_adapter.canbus'
        self.driver = canbus_driver.new(channel)
        self.driver:init()
        self.driver:set_speeds(155, 155)  -- 当前采用速率155200，单位1000，所以使用155
    end)
    if not ok then
        log:error('canbus instance [%d] init failed, error: %s', channel, msg)
        return false
    end
    return true
end

-- send a message using canbus
-- the message will be transmitted by different frames while oversize (frame size is limited in 64 Bytes)
function canbus_instance:send(msg, trans_id, dest)
    if self.state ~= definition.can_state.NORMAL then
        log:warn('current can [%s] is abnormal and cannot send message', self.channel)
        return false
    end

    if type(msg) ~= 'string' or #msg > definition.msg_max_size.CANFD then
        log:error('current message format [%s] is invalid', type(msg))
        return false
    end

    -- split msg into items, whose size of per-item is under maximum
    local msg_items = {}
    local start = 1
    while true do
        local item = msg:sub(start, definition.can_frame_max_size.CANFD + start - 1)  -- 计算长度后需要减1才是停止截取的下标
        if not item or #item == 0 then
            break
        end
        -- 需要补齐64
        if #item < definition.can_frame_max_size.CANFD then
            item = item .. string.rep(' ', definition.can_frame_max_size.CANFD - #item)
        end
        table.insert(msg_items, item)
        start = start + definition.can_frame_max_size.CANFD
    end

    -- send message with frames
    for k, v in ipairs(msg_items) do
        local can_id = definition.can_id_pattern:pack({
            frame_id = k,
            trans_id = trans_id,
            last = (k < #msg_items) and 0 or 1,
            dest_id = dest,
            src_id = self.identifier,
            eff_flag = 0,
            rtr_flag = 0,
            extend = 1
        })
        local frame = can_id .. v
        local args = {}
        local ok, err = pcall(function ()
            self.driver:lock()
            self.driver:write(frame, #frame, definition.can_frame_timeout.SEND, definition.can_frame_type.CANFD, 0)
            self.send_count = self.send_count + 1
            self.driver:unlock()
        end)
        if not ok then
            log:error('send message frame [%s] via can [%s] failed, error: %s', k, self.channel, err)
            self.send_failed = self.send_failed + 1
            return false
        end
    end
    return true
end

return canbus_instance